package com.dt.app.security.config;

import com.dt.app.api.sys.service.impl.SysUserServiceImpl;
import com.dt.app.common.constant.ConstantCode;
import com.dt.app.common.service.RedisService;
import com.dt.app.exception.CmsCodeException;
import com.dt.app.exception.CmsTokenException;
import com.dt.app.modules.base.entites.PayloadEntity;
import com.dt.app.security.handler.LoginFailureHandler;
import com.dt.app.security.rsa.RsaJwtUtils;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import static com.dt.app.common.constant.ConstantCode.*;

/**
 * Token过滤器处理器
 * @author DT
 * @date 2021/6/5 10:48
 */
@Component("cmsTokenFilter")
@ConfigurationProperties(prefix = "security")
public class CmsTokenFilter extends OncePerRequestFilter {

    private static final Logger log = LoggerFactory.getLogger(CmsTokenFilter.class);

    @Autowired
    private LoginFailureHandler loginFailureHandler;
    @Autowired
    private RsaJwtUtils rsaJwtUtils;
    @Autowired
    private SysUserServiceImpl userDetailsService;
    @Autowired
    private RedisService redisService;

    @SneakyThrows
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain){
        String url = request.getRequestURI();
        log.info("CMS请求地址：{}",url);
        if(url.equals(LOGIN_URL)){
            try{
                validate(request);
            }catch (AuthenticationException e){
                loginFailureHandler.onAuthenticationFailure(request,response,e);
                return;
            }
        }else {
            if (url.contains(API) && !url.equals(CAPTCHA) && !url.equals(DRUID_PATH)
                    && !url.equals(SWAGGER_DOC) && !url.equals(SWAGGER_RES)
                    && !url.equals(SWAGGER_API) && !url.contains(SWAGGER_SFX)
                    && !url.contains(INIT_URL) && !url.contains(WEBSOCKET_PATH)) {
                try {
                    log.info("CMS请求认证授权：{}",url);
                    authenticationToken(request);
                } catch (AuthenticationException e) {
                    loginFailureHandler.onAuthenticationFailure(request, response, e);
                    return;
                }
            }
        }
        filterChain.doFilter(request,response);
    }

    @SneakyThrows
    private void authenticationToken(HttpServletRequest request) {
        String token = request.getHeader("token");
        if (token != null) {
            PayloadEntity payloadEntity = rsaJwtUtils.verifyTokenByRsa(token, rsaJwtUtils.getDefaultRsaKey());
            String username = payloadEntity.getUsername();
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }else {
            throw new CmsTokenException("很抱歉，登录已过期，请重新登录");
        }
    }

    private void validate(HttpServletRequest request){
        String inputCode = request.getParameter("code");
        long expire = redisService.getExpire(ConstantCode.KAPTCHA_KEY);
        if(expire <= 0L){
            throw new CmsCodeException("验证码过期请重新输入!");
        }
        Object redisCode = redisService.get(ConstantCode.KAPTCHA_KEY);
        if(!inputCode.equalsIgnoreCase((String) redisCode)){
            throw new CmsCodeException("验证码输入错误!");
        }
    }
}
